Estudiante: Sebastián Cardona y Jose Miguel Millán
ID: 1094910122 y 1088334182
Email: sacardonar@uqvirtual.edu.co y josem.millanl@uqvirtual.edu.co
Docente: [Jose R. Zapata](https://joserzapata.github.io)
En el anterior experimento se entrenaron dos modelos de regresión considerando unos casos atípicos dentro del análisis, los modelos resultantes tuvieron un bajo desempeño, por lo tanto, en este experimento se eliminarán los datos atípicos encontrados.
El dataset "house data" contiene información de casas vendidas en Kansas y características descriptivas de las mismas, inicialmente se realizará una exploración de datos para poder saber la calidad del dataset iniciando con una limpieza la cual consta de eliminar duplicados, identificación de datos atípicos, nulos o mal escritos para poder tratarlos y mitigarlos ya sea con la eliminación o aplicación de métodos estadísticos con la finalidad de tener un datset listo y poder aplicar una regresión lineal para predecir los precios de venta de una casa.
%load_ext autoreload
%autoreload 2
from datetime import datetime
from pathlib import Path
import numpy as np
import pandas as pd
import plotly.express as px
import seaborn as sns
from IPython.display import display, HTML
from pandas_profiling.profile_report import ProfileReport
from sklearn.linear_model import SGDRegressor, Lasso, Ridge, LinearRegression, ElasticNet
from sklearn.metrics import r2_score
from sklearn.model_selection import cross_val_score, train_test_split, cross_validate, RepeatedKFold
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.svm import SVR
import phik
from tqdm import tqdm
from src.data.preprocessing import preprocessing
from src.features.build_features import build_features
from src.jutils.data import DataUtils
from src.jutils.visual import Plot
from src.data.procesamiento_datos import Preprocesamiento, LimpiezaCalidad, ProcesamientoDatos
import joblib
# Funciones utilizadas para realizar un preprocesamiento general.
def validar_duplicados(_df):
_filas = _df.shape[0]
_cant_duplicados = _df.duplicated().sum()
print(f'De {_filas} registros hay {_cant_duplicados} filas duplicadas, representando el {_cant_duplicados/_filas:.2%}')
def eliminar_duplicados(_df):
# Eliminando duplicados
_df = _df.drop_duplicates(keep='first')
_filas = _df.shape[0]
print(f'Después de la eliminación de duplicados, el conjunto de datos queda con {_filas} filas.')
return _df
def validar_index_duplicados(_df):
# Validando duplicados de index
_son_duplicados = _df['index'].duplicated()
_cant_duplicados = _son_duplicados.sum()
_filas = _df.shape[0]
print(f'De {_filas} registros, hay {_cant_duplicados} registros con index duplicado, que representan el {_cant_duplicados/_filas:.2%}.')
return _son_duplicados
def convertir_col_date_a_date(_df):
_df['date'] = pd.to_datetime(_df['date'], errors='coerce')
return _df
def reemplazar_valores_extremos(_df, _columnas_numericas):
_df[_columnas_numericas] = _df[_columnas_numericas].where(lambda x: x > -1e+10, other=np.nan).where(
lambda x: x < 1e+10, other=np.nan)
return _df
def reemplazar_nulos_por_la_media(_df, _columnas_numericas):
# Se reemplazan los valores nulos por la media Nota: No se considera que haya data leakage pues los valores
# reemplazados son entre registros con el mismo index y como al final se va a dejar un dataset con index únicos,
# no hay riesgo que estén tanto en el set de entrenamiento como en el de test
for columna_numerica in _columnas_numericas:
_df[columna_numerica] = _df[columna_numerica].fillna(
_df.groupby('index')[columna_numerica].transform('median'))
return _df
def reemplazar_fechas_nulas(_df):
# Reemplazando fechas nulas por la primera fecha no nula
_df['date'] = _df['date'].fillna(
_df.groupby(['index'], sort=False)['date'].apply(lambda x: x.ffill().bfill()))
return _df
def reemplazar_ceros_por_nulos(_df):
# Reemplazando ceros por valores nulos
_df[['sqft_basement', 'yr_renovated']] = _df[['sqft_basement', 'yr_renovated']].replace(0, np.nan)
return _df
# Funciones utilizadas para procesar únicamente los datos de entrenamiento
def z_score_outliers(_df, _column):
"""
Returns:
zscore, outlier
"""
# Adaptado de https://www.kaggle.com/code/shweta2407/regression-on-housing-data-accuracy-87
#creating lists to store zscore and outliers
zscore = []
isoutlier =[]
# for zscore generally taken thresholds are 2.5, 3 or 3.5 hence i took 3
threshold = 3
# calculating the mean of the passed column
mean = np.mean(_df[_column])
# calculating the standard deviation of the passed column
std = np.std(_df[_column])
for i in _df[_column]:
z = (i-mean)/std
zscore.append(z)
#if the zscore is greater than threshold = 3 that means it is an outlier
isoutlier.append(np.abs(z) > threshold)
return zscore, isoutlier
# Funciones para realizar el procesamiento de los datos antes de ingresar al modelo
def mediana_recortada_imputacion(_df, _column, _isoutlier):
mediana_recortada = _df[_column][~_isoutlier].median()
_df.loc[_isoutlier, _column] = mediana_recortada
return _df
def calculo_variables_adicionales(_df):
_df['tiene_sotano'] = (~_df['sqft_basement'].isna()).astype(int)
_df['fue_renovada'] = (~_df['yr_renovated'].isna()).astype(int)
_df['yr_date'] = _df['date'].dt.year
_df['antiguedad_venta'] = _df['yr_date'] - _df['yr_built']
return _df
def procesamiento_datos_faltantes(_df, _columnas):
_df = _df.dropna(subset=_columnas)
return _df
def clasificar_columnas(_df, _clasificacion_columnas):
_df[_clasificacion_columnas['categorica_ordinal']] = _df[_clasificacion_columnas['categorica_ordinal']].astype(int)
_df[_clasificacion_columnas['numerica_continua']] = _df[_clasificacion_columnas['numerica_continua']].astype(float)
_df[_clasificacion_columnas['numerica_discreta']] = np.floor(_df[_clasificacion_columnas['numerica_discreta']])
return _df
def imputacion_de_datos(_df, _columnas):
_df[_columnas] = _df[_columnas].fillna(0)
return _df
def profiler_to_file(_profiler, archivo):
print('Ejecutando profiler')
_profiler.to_file(du.data_folder_path.parent.joinpath('reports/' + archivo))
return True
def calcular_descriptivas(_df):
descriptivas = _df.describe()
descriptivas.loc['rango'] = descriptivas.loc['max'] - descriptivas.loc['min']
descriptivas.loc['IQR'] = descriptivas.loc['75%'] - descriptivas.loc['25%']
descriptivas.loc['coef de var'] = descriptivas.loc['std']/descriptivas.loc['mean']
descriptivas.loc['skewness'] = du.data.skew(numeric_only=True)
descriptivas.loc['kurtosis'] = du.data.kurtosis(numeric_only=True)
return descriptivas
plot = Plot()
du = DataUtils(
Path(r'..\data').resolve().absolute(),
"kc_house_dataDS.parquet",
'price',
lambda path: pd.read_parquet(path),
lambda df, path: df.to_parquet(path)
)
du.data = du.load_data(du.interim_path.joinpath(du.input_file_name))
Durante la exploración inicial se realizó la conversión de los tipos de datos y la correcta representación de datos nulos.
shape = du.data.shape
filas = shape[0]
columnas = shape[1]
print(f'El conjunto de datos se compone de {filas} filas y {columnas} columnas.')
El conjunto de datos se compone de 131994 filas y 23 columnas.
du.data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 131994 entries, 0 to 131993 Data columns (total 23 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 index 131994 non-null int64 1 zipcode 118787 non-null float64 2 grade 118717 non-null float64 3 sqft_basement 118688 non-null float64 4 view 118854 non-null float64 5 bathrooms 118689 non-null float64 6 bedrooms 118729 non-null float64 7 sqft_above 118712 non-null float64 8 sqft_living15 118814 non-null float64 9 lat 118731 non-null float64 10 waterfront 118730 non-null float64 11 floors 118791 non-null float64 12 date 127544 non-null object 13 yr_renovated 118728 non-null float64 14 yr_built 118712 non-null float64 15 long 118760 non-null float64 16 jhygtf 118728 non-null float64 17 sqft_lot 118763 non-null float64 18 price 118661 non-null float64 19 condition 118740 non-null float64 20 wertyj 131994 non-null int64 21 sqft_lot15 118739 non-null float64 22 sqft_living 118768 non-null float64 dtypes: float64(20), int64(2), object(1) memory usage: 24.2+ MB
Todas las columnas son del tipo correcto a excepción de date, se deberá hacer la conversión de este campo.
validar_duplicados(du.data)
De 131994 registros hay 1300 filas duplicadas, representando el 0.98%
du.data = eliminar_duplicados(du.data)
Después de la eliminación de duplicados, el conjunto de datos queda con 130694 filas.
# Validar indices duplicados
son_duplicados = validar_index_duplicados(du.data)
De 130694 registros, hay 108695 registros con index duplicado, que representan el 83.17%.
# Revisando los primeros registros duplicados
du.data[son_duplicados].sort_values(by='index').head()
| index | zipcode | grade | sqft_basement | view | bathrooms | bedrooms | sqft_above | sqft_living15 | lat | ... | yr_renovated | yr_built | long | jhygtf | sqft_lot | price | condition | wertyj | sqft_lot15 | sqft_living | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 44480 | 0 | 98178.0 | 7.0 | 0.0 | 0.0 | 1.0 | NaN | 1.180000e+03 | 1.340000e+03 | 47.5112 | ... | 0.0 | 1.955000e+03 | NaN | 0.0 | 5.650000e+03 | 221900.0 | 3.000000e+00 | 876543 | 5650.0 | 1180.0 |
| 59332 | 0 | 98178.0 | 7.0 | NaN | NaN | 1.0 | 3.0 | 1.180000e+03 | 1.340000e+03 | 47.5112 | ... | 0.0 | 1.955000e+03 | -122257.0 | 0.0 | 5.650000e+03 | NaN | 3.000000e+00 | 876543 | 5650.0 | 1180.0 |
| 89550 | 0 | NaN | NaN | 0.0 | 0.0 | 1.0 | 3.0 | 1.180000e+03 | NaN | NaN | ... | 0.0 | NaN | -122257.0 | 0.0 | 5.650000e+03 | 221900.0 | 3.000000e+00 | 876543 | 5650.0 | 1180.0 |
| 27240 | 0 | 98178.0 | 7.0 | 0.0 | 0.0 | 1.0 | 3.0 | 1.180000e+03 | 1.340000e+03 | 47.5112 | ... | 0.0 | 1.955000e+03 | -122257.0 | 0.0 | 5.650000e+03 | 221900.0 | 3.000000e+00 | 876543 | 5650.0 | 1180.0 |
| 50303 | 0 | 98178.0 | 7.0 | 0.0 | 0.0 | 1.0 | 3.0 | -5.432346e+10 | -5.432346e+10 | 47.5112 | ... | 0.0 | -5.432346e+10 | -122257.0 | 0.0 | -5.432346e+10 | 221900.0 | -5.432346e+10 | 876543 | 5650.0 | 1180.0 |
5 rows × 23 columns
Revisando los registros duplicados por index, se encuentra que muchas columnas tienen los mismos valores , lo único que cambia es que hay algunos faltantes y hay otros valores extremadamente bajos o altos, adicionalmente se observan algunos registros de la columna date que no son fechas. Primero se convertirá los valores de la columna date a date y los que no puedan ser convertidos se reemplazarán por valores nulos, luego se reemplazarán los valores extremos por valores nulos, luego se calculará la mediana por index para las columnas numéricas y se reemplazarán los valores nulos por estas medianas, luego se eliminarán filas duplicadas y se reevaluarán los index duplicados.
Si nuestra suposición es correcta, no importa realizar una imputación por la mediana pues todos los valores de los índices son iguales pues luego de hacer la imputación y eliminar nuevamente duplicados no deberían quedar índices duplicados, en caso de que sigan habiendo índices duplicados se deben revertir las imputaciones realizadas y buscar otra estrategia para eliminar duplicados exactos.
# Convirtiendo la columna date a datetime
du.data = convertir_col_date_a_date(du.data)
# Reemplazando valores extremos, menores a -1e+10 o mayores a 1e+10
columnas_numericas = [columna for columna in du.data.columns if columna != 'date']
du.data = reemplazar_valores_extremos(du.data, columnas_numericas)
# Se reemplazan los valores extremos por la media
# Nota: No se considera que haya data leakage pues los valores reemplazados son entre registros con el mismo index y como
# al final se va a dejar un dataset con index únicos, no hay riesgo que estén tanto en el set de entrenamiento como en el de
# test
du.data = reemplazar_nulos_por_la_media(du.data, columnas_numericas)
# Reemplazando fechas nulas por la primera fecha no nula
du.data = reemplazar_fechas_nulas(du.data)
Para las siguientes columnas, un cero representa un dato nulo, por lo tanto se reemplazarán.
# Reemplazando ceros por valores nulos
du.data = reemplazar_ceros_por_nulos(du.data)
validar_duplicados(du.data)
De 130694 registros hay 108695 filas duplicadas, representando el 83.17%
du.data = eliminar_duplicados(du.data)
Después de la eliminación de duplicados, el conjunto de datos queda con 21999 filas.
son_duplicados = validar_index_duplicados(du.data)
De 21999 registros, hay 0 registros con index duplicado, que representan el 0.00%.
Una vez validados los índices duplicados, se evidencia que la limpieza surtió efecto (Se debe implementar un control que valide esto cuando se vaya a realizar un reentrenamiento, se debe alertar cuando sigan habiendo índices duplicados e interrumpir el proceso)
# Validando columnas con valores constantes
unicos=du.data.nunique()
unicos[unicos==1]
wertyj 1 dtype: int64
La columna wertyj tiene valores constantes, por lo tanto se eliminará.
du.data = du.data.drop(columns=list(unicos[unicos==1].index))
nulos = du.data.isnull().sum()
cant_unicos = du.data.apply(lambda x: len(x.unique()))
porce = nulos/filas
nulos = pd.DataFrame({'nulos':nulos, 'porc':porce, 'cant_unicos': cant_unicos})
# Se contarán las filas que contengan algún dato nulo
al_menos_un_nulo=du.data.isnull().any(axis=1).sum()
nulos.sort_values(by='porc', ascending=False)
| nulos | porc | cant_unicos | |
|---|---|---|---|
| yr_renovated | 21069 | 0.159621 | 70 |
| sqft_basement | 13368 | 0.101277 | 306 |
| price | 30 | 0.000227 | 4027 |
| zipcode | 12 | 0.000091 | 71 |
| sqft_above | 12 | 0.000091 | 947 |
| sqft_living | 11 | 0.000083 | 1039 |
| lat | 9 | 0.000068 | 5035 |
| jhygtf | 9 | 0.000068 | 71 |
| floors | 9 | 0.000068 | 7 |
| grade | 9 | 0.000068 | 13 |
| bathrooms | 8 | 0.000061 | 31 |
| yr_built | 7 | 0.000053 | 117 |
| waterfront | 6 | 0.000045 | 3 |
| sqft_lot | 6 | 0.000045 | 9782 |
| bedrooms | 5 | 0.000038 | 14 |
| condition | 5 | 0.000038 | 6 |
| view | 4 | 0.000030 | 6 |
| date | 3 | 0.000023 | 373 |
| long | 3 | 0.000023 | 753 |
| sqft_living15 | 1 | 0.000008 | 778 |
| sqft_lot15 | 1 | 0.000008 | 8690 |
| index | 0 | 0.000000 | 21999 |
print(f'De {filas} registros, hay {al_menos_un_nulo} registros con al menos un valor nulo, representando el {al_menos_un_nulo/filas:.2%}')
De 131994 registros, hay 21531 registros con al menos un valor nulo, representando el 16.31%
Se debe tener en cuenta que para el caso de yr_renovated y sqft_basement, un valor nulo no representa necesariamente falta de información, para el caso de yr_renovated un nulo representa que esa casa nunca se renovó. Y en el caso de sqft_basement quiere decir que la casa no tiene sótano.
# Calculando variables adicionales
du.data = calculo_variables_adicionales(du.data)
# Esta vez no se tendrán en cuenta las columnas yr_renovated y sqft_basement
df = du.data.drop(columns=['yr_renovated', 'sqft_basement'])
nulos = df.isnull().sum()
cant_unicos = df.apply(lambda x: len(x.unique()))
porce = nulos/filas
nulos = pd.DataFrame({'nulos':nulos, 'porc':porce, 'cant_unicos': cant_unicos})
# Se contarán las filas que contengan algún dato nulo
al_menos_un_nulo=df.isnull().any(axis=1).sum()
nulos.sort_values(by='porc', ascending=False)
| nulos | porc | cant_unicos | |
|---|---|---|---|
| price | 30 | 0.000227 | 4027 |
| sqft_above | 12 | 0.000091 | 947 |
| zipcode | 12 | 0.000091 | 71 |
| sqft_living | 11 | 0.000083 | 1039 |
| antiguedad_venta | 10 | 0.000076 | 118 |
| floors | 9 | 0.000068 | 7 |
| grade | 9 | 0.000068 | 13 |
| jhygtf | 9 | 0.000068 | 71 |
| lat | 9 | 0.000068 | 5035 |
| bathrooms | 8 | 0.000061 | 31 |
| yr_built | 7 | 0.000053 | 117 |
| waterfront | 6 | 0.000045 | 3 |
| sqft_lot | 6 | 0.000045 | 9782 |
| bedrooms | 5 | 0.000038 | 14 |
| condition | 5 | 0.000038 | 6 |
| view | 4 | 0.000030 | 6 |
| date | 3 | 0.000023 | 373 |
| long | 3 | 0.000023 | 753 |
| yr_date | 3 | 0.000023 | 3 |
| sqft_living15 | 1 | 0.000008 | 778 |
| sqft_lot15 | 1 | 0.000008 | 8690 |
| tiene_sotano | 0 | 0.000000 | 2 |
| fue_renovada | 0 | 0.000000 | 2 |
| index | 0 | 0.000000 | 21999 |
print(f'De {filas} registros, hay {al_menos_un_nulo} registros con al menos un valor nulo, representando el {al_menos_un_nulo/filas:.2%}')
De 131994 registros, hay 109 registros con al menos un valor nulo, representando el 0.08%
Para el entrenamiento del primer modelo se eliminarán los datos nulos debido a su poca cantidad.
columnas_a_eliminar_nulos = du.data.drop(columns=['yr_renovated', 'sqft_basement']).columns
du.data = procesamiento_datos_faltantes(du.data, columnas_a_eliminar_nulos)
print(f'{du.data.shape=}')
du.data.shape=(21890, 26)
train_test, validation = train_test_split(du.data, test_size=0.2, random_state=1)
du.save_data(train_test, du.raw_train_test_path)
du.save_data(validation, du.raw_validation_path)
print(f'{train_test.shape=}')
print(f'{validation.shape=}')
train_test.shape=(17512, 26) validation.shape=(4378, 26)
du.data = du.load_data(du.raw_train_test_path)
print('Tipos de variables')
du.data.info()
Tipos de variables <class 'pandas.core.frame.DataFrame'> Int64Index: 17512 entries, 19857 to 237 Data columns (total 26 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 index 17512 non-null int64 1 zipcode 17512 non-null float64 2 grade 17512 non-null float64 3 sqft_basement 6857 non-null float64 4 view 17512 non-null float64 5 bathrooms 17512 non-null float64 6 bedrooms 17512 non-null float64 7 sqft_above 17512 non-null float64 8 sqft_living15 17512 non-null float64 9 lat 17512 non-null float64 10 waterfront 17512 non-null float64 11 floors 17512 non-null float64 12 date 17512 non-null datetime64[ns] 13 yr_renovated 732 non-null float64 14 yr_built 17512 non-null float64 15 long 17512 non-null float64 16 jhygtf 17512 non-null float64 17 sqft_lot 17512 non-null float64 18 price 17512 non-null float64 19 condition 17512 non-null float64 20 sqft_lot15 17512 non-null float64 21 sqft_living 17512 non-null float64 22 tiene_sotano 17512 non-null int32 23 fue_renovada 17512 non-null int32 24 yr_date 17512 non-null float64 25 antiguedad_venta 17512 non-null float64 dtypes: datetime64[ns](1), float64(22), int32(2), int64(1) memory usage: 3.5 MB
# Clasificación de columnas
clasificacion_columnas = {
'categorica_ordinal': ['zipcode', 'grade', 'view', 'waterfront', 'condition', 'lat', 'long'],
'fecha': ['date'],
'id': ['index'],
'numerica_continua': ['sqft_basement', 'sqft_above', 'sqft_living15', 'sqft_lot', 'price', 'sqft_lot15', 'sqft_living'],
'numerica_discreta': ['bathrooms', 'bedrooms', 'yr_renovated', 'yr_built', 'jhygtf', 'yr_date', 'antiguedad_venta', 'floors']
}
columna_salida = 'price'
columnas_a_descartar = ['date', 'index']
du.data = du.data.drop(columns=columnas_a_descartar)
columnas_entrada = du.data.drop(columns=columna_salida).columns
print(f'Columna salida: {columna_salida}')
print(f'Columnas de entrada: {columnas_entrada}')
Columna salida: price
Columnas de entrada: Index(['zipcode', 'grade', 'sqft_basement', 'view', 'bathrooms', 'bedrooms',
'sqft_above', 'sqft_living15', 'lat', 'waterfront', 'floors',
'yr_renovated', 'yr_built', 'long', 'jhygtf', 'sqft_lot', 'condition',
'sqft_lot15', 'sqft_living', 'tiene_sotano', 'fue_renovada', 'yr_date',
'antiguedad_venta'],
dtype='object')
Analisis de cada una de las variables para lograr calidad de datos en cada columna
du.data= clasificar_columnas(du.data, clasificacion_columnas)
du.data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 17512 entries, 19857 to 237 Data columns (total 24 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 zipcode 17512 non-null int32 1 grade 17512 non-null int32 2 sqft_basement 6857 non-null float64 3 view 17512 non-null int32 4 bathrooms 17512 non-null float64 5 bedrooms 17512 non-null float64 6 sqft_above 17512 non-null float64 7 sqft_living15 17512 non-null float64 8 lat 17512 non-null int32 9 waterfront 17512 non-null int32 10 floors 17512 non-null float64 11 yr_renovated 732 non-null float64 12 yr_built 17512 non-null float64 13 long 17512 non-null int32 14 jhygtf 17512 non-null float64 15 sqft_lot 17512 non-null float64 16 price 17512 non-null float64 17 condition 17512 non-null int32 18 sqft_lot15 17512 non-null float64 19 sqft_living 17512 non-null float64 20 tiene_sotano 17512 non-null int32 21 fue_renovada 17512 non-null int32 22 yr_date 17512 non-null float64 23 antiguedad_venta 17512 non-null float64 dtypes: float64(15), int32(9) memory usage: 2.7 MB
box_plots = [plot.box(du.data, y=columna) for columna in clasificacion_columnas['numerica_continua']]
histograms = [plot.histogram(du.data, x=columna, text_auto=False) for columna in clasificacion_columnas['numerica_continua']]
plot.grid_subplot(*box_plots, cols= 3, title= 'Distribución inicial',titles=clasificacion_columnas['numerica_continua']).show()
plot.grid_subplot(*histograms, cols= 3, title= 'Distribución inicial',titles=clasificacion_columnas['numerica_continua']).show()
# Eliminando registros identificados como outliers según el z_score
du.data = du.data[~pd.Series(z_score_outliers(du.data, 'price')[1], index=du.data.index)]
du.data = du.data[~pd.Series(z_score_outliers(du.data, 'sqft_lot')[1], index=du.data.index)]
du.data = du.data[~pd.Series(z_score_outliers(du.data, 'sqft_lot15')[1], index=du.data.index)]
box_plots = [plot.box(du.data, y=columna) for columna in clasificacion_columnas['numerica_continua']]
histograms = [plot.histogram(du.data, x=columna, text_auto=False) for columna in clasificacion_columnas['numerica_continua']]
plot.grid_subplot(*box_plots, cols= 3, title= 'Distribución inicial',titles=clasificacion_columnas['numerica_continua']).show()
plot.grid_subplot(*histograms, cols= 3, title= 'Distribución inicial',titles=clasificacion_columnas['numerica_continua']).show()
Para las variables numericas_discreta se analizarán sus rangos
pd.DataFrame({
'min':du.data[clasificacion_columnas['numerica_discreta']].min(),
'max':du.data[clasificacion_columnas['numerica_discreta']].max(),
'nulos':du.data[clasificacion_columnas['numerica_discreta']].isna().sum()
})
| min | max | nulos | |
|---|---|---|---|
| bathrooms | 0.0 | 8.0 | 0 |
| bedrooms | 0.0 | 33.0 | 0 |
| yr_renovated | 1934.0 | 2015.0 | 15905 |
| yr_built | 1900.0 | 2015.0 | 0 |
| jhygtf | 0.0 | 2015.0 | 0 |
| yr_date | 2014.0 | 2015.0 | 0 |
| antiguedad_venta | -1.0 | 115.0 | 0 |
| floors | 1.0 | 3.0 | 0 |
Definicion de outliers:
Se considerarán outliers:
bathrooms_outliers = (du.data['bathrooms']==0) | (du.data['bathrooms'] > 4)
bedrooms_outliers = (du.data['bedrooms']==0) | (du.data['bedrooms'] > 5)
son_outliers = bathrooms_outliers | bedrooms_outliers
cant_outliers = son_outliers.sum()
print(f'Con estas características hay: {cant_outliers} outliers representando el {cant_outliers/len(son_outliers):.2%}')
Con estas características hay: 324 outliers representando el 1.96%
Imputación de outliers: Se reemplazarán los outliers calculando la mediana recortada la cual se realiza teniendo en cuenta únicamente los inliers
du.data = mediana_recortada_imputacion(du.data, 'bathrooms', bathrooms_outliers)
du.data = mediana_recortada_imputacion(du.data, 'bedrooms', bedrooms_outliers)
# Revisando los datos después de la imputación
plot.grid_subplot(
*[plot.bar(du.data, x=column, max_bins=10) for column in clasificacion_columnas['numerica_discreta']],
cols=3,
titles=clasificacion_columnas['numerica_discreta']).show()
pd.DataFrame({
'min':du.data[clasificacion_columnas['numerica_discreta']].min(),
'max':du.data[clasificacion_columnas['numerica_discreta']].max(),
'nulos':du.data[clasificacion_columnas['numerica_discreta']].isna().sum()
})
| min | max | nulos | |
|---|---|---|---|
| bathrooms | 1.0 | 4.0 | 0 |
| bedrooms | 1.0 | 5.0 | 0 |
| yr_renovated | 1934.0 | 2015.0 | 15905 |
| yr_built | 1900.0 | 2015.0 | 0 |
| jhygtf | 0.0 | 2015.0 | 0 |
| yr_date | 2014.0 | 2015.0 | 0 |
| antiguedad_venta | -1.0 | 115.0 | 0 |
| floors | 1.0 | 3.0 | 0 |
Para las columnas categóricas se revisarán sus valores únicos.
pd.DataFrame({
'distinct_count':du.data[clasificacion_columnas['categorica_ordinal']].nunique(),
'nulos':du.data[clasificacion_columnas['categorica_ordinal']].isna().sum()
})
| distinct_count | nulos | |
|---|---|---|
| zipcode | 70 | 0 |
| grade | 12 | 0 |
| view | 5 | 0 |
| waterfront | 2 | 0 |
| condition | 5 | 0 |
| lat | 429 | 0 |
| long | 608 | 0 |
No se evidencian datos atípicos. lat, long y zipcode tienen demasiada cardinalidad pero es normal por ser datos de ubicación.
Se realizará un perfilado de los datos para identificar problemas de calidad no identificados anteriormente.
profiler = ProfileReport(du.data, explorative=True)
profiler_to_file(profiler, '2_0_0 Perfilado inicial.html')
Ejecutando profiler
Summarize dataset: 0%| | 0/5 [00:00<?, ?it/s]
Generate report structure: 0%| | 0/1 [00:00<?, ?it/s]
Render HTML: 0%| | 0/1 [00:00<?, ?it/s]
Export report to file: 0%| | 0/1 [00:00<?, ?it/s]
True
# Se utilizará el índice de correlación phik pues este permite calcular la correlación entre variables numéricas y categóricas al tiempo.
# Este indice funciona mejor cuando no hay valores nulos, por lo tanto se reemplazarán los valores nulos por 0
cor_mat = du.data.fillna(0).phik_matrix()
interval columns not set, guessing: ['zipcode', 'grade', 'sqft_basement', 'view', 'bathrooms', 'bedrooms', 'sqft_above', 'sqft_living15', 'lat', 'waterfront', 'floors', 'yr_renovated', 'yr_built', 'long', 'jhygtf', 'sqft_lot', 'price', 'condition', 'sqft_lot15', 'sqft_living', 'tiene_sotano', 'fue_renovada', 'yr_date', 'antiguedad_venta']
# Primero se analizará la correlación entre las variables de entrada y se descartarán aquellas con una correlación superior a 0.9
(cor_mat.loc[columnas_entrada, columnas_entrada] > 0.9).sum().sort_values(ascending=False)
yr_renovated 3 fue_renovada 3 jhygtf 3 yr_built 2 tiene_sotano 2 sqft_living 2 antiguedad_venta 2 sqft_above 2 sqft_basement 2 sqft_living15 1 lat 1 waterfront 1 floors 1 grade 1 long 1 bedrooms 1 sqft_lot 1 condition 1 sqft_lot15 1 bathrooms 1 view 1 yr_date 1 zipcode 1 dtype: int64
# Se analizarán con mas detalle aquellas que tienen una alta correlación con más de 1 columna (Consigo misma)
columnas_alta_correlacion = ['yr_renovated', 'fue_renovada', 'jhygtf', 'yr_built', 'tiene_sotano', 'sqft_living', 'antiguedad_venta', 'sqft_above', 'sqft_basement']
px.imshow(
cor_mat.loc[columnas_alta_correlacion, columnas_alta_correlacion].round(2),
color_continuous_scale= 'blues',
text_auto=True).show()
La primera columna a eliminar es jhygtf se observa que es la misma variable que yr_renovated. Aunque la columna fue_renovada está calculada con base en yr_renovated se conservarán ambas para posteriormente elegir con cual de las dos se puede obtener un mejor modelo.
Quedan altas correlaciones entre las siguientes columnas:
Para las restantes, se conservarán todas para después mediante el cálculo de la importancia de variables escoger cual tiene un mejor poder predictivo.
Entonces solo se eliminará la columna jhygtf
du.data = du.data.drop(columns=['jhygtf'])
# Correlación de todas las variables de entrada con respecto a la salida
cor_mat.loc['price', du.data.columns].sort_values(ascending=False)
price 1.000000 sqft_living 0.849294 sqft_above 0.705247 sqft_basement 0.700367 grade 0.520938 sqft_living15 0.412334 bathrooms 0.397703 waterfront 0.335567 view 0.324725 floors 0.269777 sqft_lot15 0.200437 bedrooms 0.191491 sqft_lot 0.144752 zipcode 0.130665 fue_renovada 0.114474 yr_renovated 0.114474 tiene_sotano 0.107443 antiguedad_venta 0.070706 yr_built 0.069420 condition 0.042359 long 0.006108 lat 0.000000 yr_date NaN Name: price, dtype: float64
Se calculará la importancia de las variables de entrada con respecto al precio adicionando una columna dummy que contrendrá valores aleatorios y se eliminarán aquellas variables cuya importancia sea inferior a la columna aleatoria (Al ser esta aleatoria, sabemos desde el principio que esta no puede ser una buena predictora).
Para realizar este calculo se utilizará un regresor lineal, se debe escalar primero las variables de entrada para poder comparar sus coeficientes.
pipeline_features = make_pipeline(StandardScaler(), LinearRegression())
X = du.data.fillna(0).drop(columns='price')
X['random'] = np.random.normal(size=(X.shape[0], 1))
y = du.data['price']
cv_model = cross_validate(
pipeline_features, X, y, cv=RepeatedKFold(n_splits=5, n_repeats=5),
return_estimator=True, n_jobs=2
)
coefs = pd.DataFrame(
[model[1].coef_ for model in cv_model['estimator']],
columns=X.columns
)
print()
print(f'mean_test_score: {np.mean(cv_model["test_score"])}')
print(f'std_test_score : {np.std(cv_model["test_score"])}')
mean_test_score: 0.6214407737722989 std_test_score : 0.01497525767408363
coefs
| zipcode | grade | sqft_basement | view | bathrooms | bedrooms | sqft_above | sqft_living15 | lat | waterfront | ... | long | sqft_lot | condition | sqft_lot15 | sqft_living | tiene_sotano | fue_renovada | yr_date | antiguedad_venta | random | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | -928.849420 | 127911.063989 | -6.816505e+15 | 21764.050490 | 18562.852166 | -33535.352087 | -1.226075e+16 | 26147.192583 | -1566.817927 | 55822.549942 | ... | 1164.492424 | -319.083534 | 16310.746474 | -17700.577551 | 1.353284e+16 | 4034.108721 | -1.024760e+06 | -2.846674e+16 | 1.785247e+18 | 1644.640830 |
| 1 | -714.470424 | 121848.585009 | -9.217886e+15 | 21925.745502 | 12831.434399 | -35779.143342 | -1.663493e+16 | 26116.859636 | -896.963165 | 50781.893791 | ... | 590.501108 | 2067.988870 | 16807.804131 | -21561.852653 | 1.831146e+16 | 7045.712907 | -1.074064e+06 | -9.500696e+12 | 5.954164e+14 | -1174.480089 |
| 2 | -2812.528204 | 127427.710260 | 2.599369e+04 | 27469.880684 | 16752.555675 | -32696.696563 | 5.430017e+04 | 26200.188408 | -296.850982 | 38672.361841 | ... | -43.337686 | -11.834184 | 17648.004278 | -18599.128857 | 6.260284e+04 | 9066.960447 | -1.185298e+06 | 8.804761e+03 | 4.629921e+04 | 1397.522062 |
| 3 | -1800.803810 | 127439.434106 | 4.155630e+17 | 23472.361688 | 16006.694716 | -36995.768156 | 7.544863e+17 | 21780.614846 | -660.783943 | 51985.013475 | ... | -523.680705 | 3618.438229 | 16127.161013 | -22834.763044 | -8.317639e+17 | 6411.516710 | -1.022589e+06 | -1.373314e+15 | 8.683376e+16 | 1136.302451 |
| 4 | -1030.333390 | 127356.386421 | -5.457488e+16 | 26074.120365 | 12918.508967 | -35987.814267 | -9.871722e+16 | 20099.888336 | -1833.790627 | 53413.953926 | ... | 1189.824235 | -1615.490127 | 17116.032711 | -17844.794544 | 1.088764e+17 | 7405.514338 | -9.442235e+05 | -1.236855e+16 | 7.749781e+17 | 992.394278 |
| 5 | -2216.117643 | 126061.003727 | 1.705394e+16 | 26651.564133 | 18172.994020 | -36586.363667 | 3.064624e+16 | 23022.486566 | -522.579869 | 54541.627745 | ... | 1325.366516 | 1557.688254 | 17518.994051 | -21344.930278 | -3.381665e+16 | 6316.096928 | -1.231295e+06 | -4.932653e+15 | 3.116065e+17 | 484.050579 |
| 6 | -2768.984450 | 126732.720084 | 8.798511e+16 | 23790.333113 | 14962.330271 | -33847.603210 | 1.585937e+17 | 27272.911168 | -806.475738 | 45254.546531 | ... | -1114.635771 | 2319.312912 | 16814.681043 | -19228.063129 | -1.744410e+17 | 6470.884857 | -8.103338e+05 | -7.304742e+13 | 4.579920e+15 | 1720.788299 |
| 7 | 316.283344 | 125913.836172 | 1.893366e+17 | 22351.003338 | 13714.404167 | -33694.985034 | 3.440806e+17 | 24593.385686 | -1966.689701 | 45578.278950 | ... | 2170.588842 | 3029.593472 | 18139.224348 | -22457.019945 | -3.776510e+17 | 4933.863389 | -9.298487e+05 | -1.318006e+14 | 8.315558e+15 | 340.368479 |
| 8 | -1607.384227 | 127452.329407 | 5.240559e+17 | 23222.018038 | 15946.453687 | -33467.802338 | 9.389098e+17 | 23452.832950 | -158.339158 | 52396.258469 | ... | 369.507031 | -2481.260400 | 15729.161945 | -16325.753114 | -1.034932e+18 | 8332.658306 | -1.156012e+06 | -9.414543e+15 | 5.873869e+17 | 751.039117 |
| 9 | -1073.331328 | 125079.437706 | 3.501386e+17 | 24608.863813 | 15183.938738 | -37669.921174 | 6.356924e+17 | 21921.124482 | -1761.619703 | 53343.366908 | ... | 177.310515 | -304.430198 | 15482.036514 | -19481.205831 | -7.026013e+17 | 8152.153794 | -1.139108e+06 | -2.108399e+14 | 1.322500e+16 | 1050.661550 |
| 10 | -1573.831310 | 125509.295107 | -4.777925e+17 | 27510.250748 | 18766.298303 | -34708.592071 | -8.656818e+17 | 25163.039962 | -1767.823299 | 42968.660251 | ... | 791.803964 | -22.170483 | 16842.019553 | -19421.111828 | 9.503048e+17 | 8604.398252 | -8.583536e+05 | 2.918965e+14 | -1.835426e+16 | 1214.277821 |
| 11 | -1625.864453 | 126086.386710 | -6.287297e+17 | 23120.158922 | 12116.716990 | -35713.889585 | -1.147516e+18 | 21305.687957 | -442.342878 | 48304.393709 | ... | -271.465928 | 512.132469 | 18486.103717 | -19566.213445 | 1.260138e+18 | 6882.378848 | -1.237401e+06 | 1.498788e+15 | -9.405432e+16 | -1436.728216 |
| 12 | -425.114363 | 127960.003299 | 2.768217e+04 | 22095.752574 | 18086.367693 | -32583.591832 | 5.720719e+04 | 27296.244284 | 137.025176 | 56474.030389 | ... | 913.333366 | 4023.317676 | 17449.517906 | -23002.402732 | 6.576094e+04 | 9101.562248 | -1.114242e+06 | 1.083153e+04 | 4.671939e+04 | 1772.798008 |
| 13 | -2423.489226 | 125509.259394 | 3.580909e+04 | 23087.416693 | 14881.073450 | -37445.199150 | 6.018348e+04 | 22824.544843 | -1170.732620 | 48690.075253 | ... | 341.110157 | -1892.084714 | 14967.951478 | -16277.490141 | 7.259350e+04 | 5351.287032 | -1.121502e+06 | 9.166875e+03 | 4.884093e+04 | 1586.878231 |
| 14 | -1392.797601 | 124720.689695 | 3.574875e+04 | 24016.574250 | 13531.006460 | -35304.634545 | 5.809826e+04 | 24279.458952 | -1547.341730 | 54820.246505 | ... | 667.661493 | 1160.002721 | 15899.790641 | -20006.128526 | 7.062126e+04 | 3743.182803 | -8.930083e+05 | 1.006998e+04 | 4.686217e+04 | 1530.956024 |
| 15 | -2908.743843 | 123397.046494 | 2.590062e+18 | 26266.403695 | 20560.544071 | -29001.221976 | 4.657491e+18 | 27135.412678 | -1662.820529 | 50517.524155 | ... | 328.397750 | 101.059992 | 16569.827593 | -17116.681673 | -5.124114e+18 | 8266.921726 | -1.267322e+06 | -2.093479e+15 | 1.313977e+17 | 1549.016677 |
| 16 | -852.366118 | 124510.167909 | 1.484418e+18 | 24024.198909 | 18258.395084 | -37883.848081 | 2.682789e+18 | 24822.885723 | -1665.850191 | 44949.066574 | ... | 291.750644 | 1674.342308 | 15631.548548 | -19915.927571 | -2.951772e+18 | 7366.282944 | -8.973885e+05 | 3.306861e+14 | -2.083475e+16 | -972.293456 |
| 17 | -1675.717135 | 130191.090425 | -3.175311e+17 | 21776.767767 | 11624.179142 | -36873.673162 | -5.785848e+17 | 24120.293436 | -258.488291 | 55524.785853 | ... | 365.923967 | 828.569763 | 17816.943474 | -21934.200926 | 6.369843e+17 | 6279.746241 | -1.064024e+06 | -3.484648e+14 | 2.197829e+16 | 236.204573 |
| 18 | -1226.393532 | 127689.929239 | 1.269115e+18 | 26306.892307 | 16442.700035 | -34195.082588 | 2.278255e+18 | 24098.767777 | -843.364732 | 49351.362006 | ... | -663.655001 | 533.133992 | 17675.570226 | -18390.300326 | -2.512021e+18 | 6647.712080 | -9.912322e+05 | -3.220481e+15 | 2.020717e+17 | 3001.136510 |
| 19 | -1371.581520 | 127115.413956 | -1.140197e+16 | 23045.334414 | 13089.689554 | -36630.991307 | -2.053732e+16 | 20600.034599 | -403.065697 | 49822.692225 | ... | 365.117461 | 2151.010547 | 16520.716764 | -21839.303150 | 2.268144e+16 | 3435.860426 | -1.112920e+06 | -1.366462e+13 | 8.537728e+14 | 1117.122001 |
| 20 | -1260.971953 | 126191.862777 | 5.197256e+17 | 24539.111413 | 17433.842626 | -34962.376872 | 9.351875e+17 | 23934.835455 | -1052.514806 | 53613.968389 | ... | 734.510733 | 1537.288447 | 16964.710124 | -20692.908677 | -1.030593e+18 | 6196.449917 | -1.143419e+06 | -8.084381e+14 | 5.053966e+16 | 2585.446972 |
| 21 | -2497.732855 | 128874.033819 | 3.156448e+04 | 22361.614047 | 13832.745967 | -36844.661170 | 6.301624e+04 | 19164.540833 | -367.554579 | 53295.157585 | ... | 1313.053486 | -743.774658 | 16658.608568 | -18110.698286 | 7.284091e+04 | 7989.497792 | -9.300871e+05 | 1.039730e+04 | 4.763030e+04 | -102.755138 |
| 22 | -1045.828477 | 127217.138240 | 1.896872e+18 | 26067.926634 | 13414.255697 | -34540.235009 | 3.431363e+18 | 25907.374172 | -1680.044077 | 50682.694944 | ... | 2165.687390 | 1865.477780 | 16869.828708 | -21654.269985 | -3.772668e+18 | 5474.368234 | -8.008335e+05 | -2.563891e+14 | 1.615678e+16 | 1464.077725 |
| 23 | -2273.995789 | 123078.628472 | -9.420465e+17 | 25832.367831 | 13979.223908 | -34845.646345 | -1.699607e+18 | 22747.351363 | -2133.183683 | 45366.484451 | ... | 395.999894 | 1403.161789 | 16381.193068 | -21151.766797 | 1.879056e+18 | 5004.897824 | -1.048460e+06 | 2.620299e+15 | -1.640383e+17 | -862.690662 |
| 24 | -2785.093771 | 125208.514953 | 2.408115e+16 | 21477.992564 | 18480.121447 | -34342.835451 | 4.369470e+16 | 28327.531385 | -663.943784 | 48368.836141 | ... | -1698.430768 | 654.849008 | 16306.960533 | -18646.232498 | -4.785104e+16 | 8917.854051 | -1.282027e+06 | 5.195015e+16 | -3.266346e+18 | 183.098308 |
25 rows × 23 columns
px.box(coefs, orientation='h', title='Importancia de los coeficientes y sus variaciones')
# Debido a que no se normalizó el precio, se dividirán los coeficientes por la media del precio para tenerlos en una escala mas
# manejable
coefs_resumen = pd.DataFrame({
'variacion': (coefs.std()/du.data['price'].mean()),
'media': coefs.mean()/du.data['price'].mean(),
'media_absoluta': coefs.abs().mean()/du.data['price'].mean()
})
coef_variacion = coefs_resumen['variacion'].sort_values(ascending=False)
coef_variacion
sqft_living 3.042883e+12 sqft_above 2.764742e+12 antiguedad_venta 1.548154e+12 yr_built 1.548112e+12 sqft_basement 1.532987e+12 yr_date 2.464350e+10 yr_renovated 2.770194e-01 fue_renovada 2.764609e-01 waterfront 8.773718e-03 bathrooms 4.827334e-03 sqft_living15 4.744334e-03 bedrooms 3.910513e-03 sqft_lot15 3.896441e-03 floors 3.759966e-03 view 3.733161e-03 grade 3.668911e-03 tiene_sotano 3.258471e-03 sqft_lot 3.176893e-03 random 2.193772e-03 long 1.747957e-03 condition 1.688643e-03 zipcode 1.620135e-03 lat 1.306657e-03 Name: variacion, dtype: float64
Para aquellas variables con demasiada variación no es confiable tomar el promedio como su importancia, por lo tanto no serán eliminadas.
Se analizarán aquellas con una variación inferior a 0.7
coefs_confiables = coef_variacion[coef_variacion <= 0.7]
coefs_confiables
yr_renovated 0.277019 fue_renovada 0.276461 waterfront 0.008774 bathrooms 0.004827 sqft_living15 0.004744 bedrooms 0.003911 sqft_lot15 0.003896 floors 0.003760 view 0.003733 grade 0.003669 tiene_sotano 0.003258 sqft_lot 0.003177 random 0.002194 long 0.001748 condition 0.001689 zipcode 0.001620 lat 0.001307 Name: variacion, dtype: float64
px.box(coefs[coefs_confiables.index], orientation='h', title='Importancia de los coeficientes y sus variaciones').show()
px.box(coefs.abs()[coefs_confiables.index], orientation='h', title='Importancia absoluta de los coeficientes y sus variaciones').show()
De esta forma se pueden eliminar aquellas que sean consistentemente peor que la columna random.
Al analizar el gráfico, no se pueden identificar variables con un peor desempeño que random.
Se usará f_regression de scikit learn para calcular la importancia de las variables.
from sklearn.feature_selection import f_regression
f_statistic, p_values = f_regression(X, y)
pd.DataFrame({'f_statistic': f_statistic, 'p_value': p_values.round(3)}, index=X.columns).sort_values(by='f_statistic', ascending=False)
| f_statistic | p_value | |
|---|---|---|
| sqft_living | 13633.682142 | 0.000 |
| grade | 11295.852987 | 0.000 |
| sqft_above | 7942.786595 | 0.000 |
| sqft_living15 | 7622.345514 | 0.000 |
| bathrooms | 3727.835576 | 0.000 |
| view | 2569.742164 | 0.000 |
| sqft_basement | 1741.583489 | 0.000 |
| bedrooms | 1368.668278 | 0.000 |
| waterfront | 1276.563745 | 0.000 |
| floors | 842.686506 | 0.000 |
| tiene_sotano | 509.942279 | 0.000 |
| sqft_lot15 | 363.784440 | 0.000 |
| sqft_lot | 331.488959 | 0.000 |
| yr_renovated | 224.215157 | 0.000 |
| fue_renovada | 223.038145 | 0.000 |
| zipcode | 36.987037 | 0.000 |
| condition | 35.840032 | 0.000 |
| antiguedad_venta | 34.136911 | 0.000 |
| yr_built | 34.030063 | 0.000 |
| lat | 0.912105 | 0.340 |
| long | 0.479515 | 0.489 |
| yr_date | 0.350826 | 0.554 |
| random | 0.348072 | 0.555 |
Con esta técnica, se puede determinar el umbral desde el que se van a descartar las variables, se observa que ninguna variable tiene un poder predictivo peor que la variable random.
Se puede ver que la importancia de las variables lat, long y yr_date no son confiables al tener un p-value mayor a 0.05
Adicionalmente, podemos determinar que de las variables de entrada altamente correlacionadas, se pueden eliminar las siguientes
Para yr_renovated y fue_renovada la diferencia es muy poca, por lo tanto se escogerá fue_renovada por la alta cantidad de nulos de yr_renovated
Por lo tanto se descartarán las siguientes columnas:
lat, long, yr_date, yr_built, tiene_sotano, sqft_above, sqft_basement, yr_built y yr_renovated
du.data = du.data.drop(columns=['lat', 'long', 'yr_date', 'yr_built', 'tiene_sotano', 'sqft_above', 'sqft_basement', 'yr_built', 'yr_renovated'])
Ya que se eliminaron las columnas yr_renovated y sqft_basement, se puede omitir este paso pues ya no quedan mas nulos.
du.data.isnull().sum().sort_values(ascending=False)
zipcode 0 grade 0 view 0 bathrooms 0 bedrooms 0 sqft_living15 0 waterfront 0 floors 0 sqft_lot 0 price 0 condition 0 sqft_lot15 0 sqft_living 0 fue_renovada 0 antiguedad_venta 0 dtype: int64
Estadistico Descriptico y Analisis
| Tendencia Central | Medida de Dispersión | Visualizacion | |:-----------------:|:-------------------------:|:-------------:| | Media | Rango | Histogramas | | Mediana | Cuartiles | Boxplots | | Moda | Rango inter cuartil (IQR) | | | Minimo | Varianza | | | Maximo | Desviacion Estandard | | | . | Skewness | | | . | Kurtosis | |
calcular_descriptivas(du.data)
| zipcode | grade | view | bathrooms | bedrooms | sqft_living15 | waterfront | floors | sqft_lot | price | condition | sqft_lot15 | sqft_living | fue_renovada | antiguedad_venta | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 1.655400e+04 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 |
| mean | 98079.010269 | 7.583968 | 0.200254 | 1.713121 | 3.306572 | 1944.150840 | 0.005739 | 1.436873 | 9934.879667 | 5.106034e+05 | 3.408058 | 8996.247856 | 2016.595143 | 0.039205 | 43.645524 |
| std | 53.580311 | 1.103965 | 0.704138 | 0.674940 | 0.821211 | 649.237427 | 0.075539 | 0.552370 | 10957.363161 | 3.238058e+05 | 0.648368 | 7636.814694 | 848.705398 | 0.194088 | 29.345804 |
| min | 98001.000000 | 1.000000 | 0.000000 | 1.000000 | 1.000000 | 460.000000 | 0.000000 | 1.000000 | 520.000000 | 7.500000e+04 | 1.000000 | 659.000000 | 290.000000 | 0.000000 | -1.000000 |
| 25% | 98033.000000 | 7.000000 | 0.000000 | 1.000000 | 3.000000 | 1470.000000 | 0.000000 | 1.000000 | 5000.000000 | 3.160000e+05 | 3.000000 | 5011.250000 | 1400.000000 | 0.000000 | 18.000000 |
| 50% | 98070.000000 | 7.000000 | 0.000000 | 2.000000 | 3.000000 | 1810.000000 | 0.000000 | 1.000000 | 7480.000000 | 4.400000e+05 | 3.000000 | 7500.000000 | 1880.000000 | 0.000000 | 40.000000 |
| 75% | 98118.000000 | 8.000000 | 0.000000 | 2.000000 | 4.000000 | 2300.000000 | 0.000000 | 2.000000 | 10140.000000 | 6.200000e+05 | 4.000000 | 9750.000000 | 2478.750000 | 0.000000 | 63.000000 |
| max | 98199.000000 | 13.000000 | 4.000000 | 4.000000 | 5.000000 | 5790.000000 | 1.000000 | 3.000000 | 137214.000000 | 7.700000e+06 | 5.000000 | 57140.000000 | 12050.000000 | 1.000000 | 115.000000 |
| rango | 198.000000 | 12.000000 | 4.000000 | 3.000000 | 4.000000 | 5330.000000 | 1.000000 | 2.000000 | 136694.000000 | 7.625000e+06 | 4.000000 | 56481.000000 | 11760.000000 | 1.000000 | 116.000000 |
| IQR | 85.000000 | 1.000000 | 0.000000 | 1.000000 | 1.000000 | 830.000000 | 0.000000 | 1.000000 | 5140.000000 | 3.040000e+05 | 1.000000 | 4738.750000 | 1078.750000 | 0.000000 | 45.000000 |
| coef de var | 0.000546 | 0.145566 | 3.516232 | 0.393982 | 0.248357 | 0.333944 | 13.162944 | 0.384425 | 1.102919 | 6.341630e-01 | 0.190246 | 0.848889 | 0.420861 | 4.950597 | 0.672367 |
| skewness | 0.372547 | 0.716523 | 3.707596 | 0.644071 | 0.045938 | 1.059620 | 13.087759 | 0.776642 | 4.698177 | 4.658301e+00 | 1.038248 | 3.168057 | 1.323768 | 4.748876 | 0.448007 |
| kurtosis | -0.884930 | 1.371933 | 13.379400 | 0.237942 | -0.103048 | 1.455089 | 169.309900 | -0.465117 | 31.240935 | 5.059510e+01 | 0.540616 | 11.847458 | 4.451355 | 20.554309 | -0.669613 |
# Actualizando la clasificación de columnas para dejar solo las que están en el dataframe
clasificacion_columnas['numerica_continua'] = list(set(clasificacion_columnas['numerica_continua']).intersection(du.data.columns))
clasificacion_columnas['categorica_ordinal'] = list(set(clasificacion_columnas['categorica_ordinal']).intersection(du.data.columns))
clasificacion_columnas['numerica_discreta'] = list(set(clasificacion_columnas['numerica_discreta']).intersection(du.data.columns))
columnas_a_graficar = clasificacion_columnas['numerica_continua']
plots1 = [plot.box(du.data, y=variable_numerica) for variable_numerica in columnas_a_graficar]
plot.grid_subplot(*plots1, cols=3, title='Diagramas de cajas y bigotes', titles=columnas_a_graficar).show()
plots2 = [plot.histogram(du.data, x=variable_numerica) for variable_numerica in columnas_a_graficar]
plot.grid_subplot(*plots2, cols=3, title='Histogramas', titles=columnas_a_graficar).show()
Se observan datos asimétricos para todas las columnas numéricas.
resumen = []
for variable_categorica in clasificacion_columnas['categorica_ordinal']:
col = du.data[variable_categorica]
elms_cat = col.groupby(by=col).agg('count')
total = elms_cat.sum()
porc = elms_cat / total
porc.name = 'porc'
df = pd.DataFrame([elms_cat, porc]).transpose()
resumen.append(df)
for tabla in resumen:
display(HTML(tabla.to_html()))
variable = tabla.columns[0]
fig = px.bar(tabla[variable], orientation='h', title=str(variable))
fig.show()
| view | porc | |
|---|---|---|
| view | ||
| 0 | 15123.0 | 0.913556 |
| 1 | 251.0 | 0.015162 |
| 2 | 666.0 | 0.040232 |
| 3 | 324.0 | 0.019572 |
| 4 | 190.0 | 0.011478 |
| condition | porc | |
|---|---|---|
| condition | ||
| 1 | 23.0 | 0.001389 |
| 2 | 126.0 | 0.007611 |
| 3 | 10763.0 | 0.650175 |
| 4 | 4357.0 | 0.263199 |
| 5 | 1285.0 | 0.077625 |
| zipcode | porc | |
|---|---|---|
| zipcode | ||
| 98001 | 291.0 | 0.017579 |
| 98002 | 160.0 | 0.009665 |
| 98003 | 213.0 | 0.012867 |
| 98004 | 205.0 | 0.012384 |
| 98005 | 132.0 | 0.007974 |
| 98006 | 377.0 | 0.022774 |
| 98007 | 113.0 | 0.006826 |
| 98008 | 226.0 | 0.013652 |
| 98010 | 69.0 | 0.004168 |
| 98011 | 150.0 | 0.009061 |
| 98014 | 65.0 | 0.003927 |
| 98019 | 131.0 | 0.007913 |
| 98022 | 140.0 | 0.008457 |
| 98023 | 399.0 | 0.024103 |
| 98024 | 31.0 | 0.001873 |
| 98027 | 265.0 | 0.016008 |
| 98028 | 220.0 | 0.013290 |
| 98029 | 268.0 | 0.016189 |
| 98030 | 198.0 | 0.011961 |
| 98031 | 219.0 | 0.013229 |
| 98032 | 106.0 | 0.006403 |
| 98033 | 307.0 | 0.018545 |
| 98034 | 446.0 | 0.026942 |
| 98038 | 431.0 | 0.026036 |
| 98039 | 29.0 | 0.001752 |
| 98040 | 187.0 | 0.011296 |
| 98042 | 431.0 | 0.026036 |
| 98045 | 150.0 | 0.009061 |
| 98052 | 467.0 | 0.028211 |
| 98053 | 287.0 | 0.017337 |
| 98055 | 221.0 | 0.013350 |
| 98056 | 322.0 | 0.019451 |
| 98058 | 361.0 | 0.021807 |
| 98059 | 381.0 | 0.023016 |
| 98065 | 238.0 | 0.014377 |
| 98070 | 54.0 | 0.003262 |
| 98072 | 216.0 | 0.013048 |
| 98074 | 336.0 | 0.020297 |
| 98075 | 274.0 | 0.016552 |
| 98077 | 131.0 | 0.007913 |
| 98092 | 250.0 | 0.015102 |
| 98102 | 88.0 | 0.005316 |
| 98103 | 477.0 | 0.028815 |
| 98105 | 166.0 | 0.010028 |
| 98106 | 275.0 | 0.016612 |
| 98107 | 211.0 | 0.012746 |
| 98108 | 153.0 | 0.009242 |
| 98109 | 85.0 | 0.005135 |
| 98112 | 166.0 | 0.010028 |
| 98115 | 476.0 | 0.028754 |
| 98116 | 259.0 | 0.015646 |
| 98117 | 442.0 | 0.026700 |
| 98118 | 406.0 | 0.024526 |
| 98119 | 141.0 | 0.008518 |
| 98122 | 209.0 | 0.012625 |
| 98125 | 331.0 | 0.019995 |
| 98126 | 273.0 | 0.016491 |
| 98133 | 402.0 | 0.024284 |
| 98136 | 209.0 | 0.012625 |
| 98144 | 254.0 | 0.015344 |
| 98146 | 228.0 | 0.013773 |
| 98148 | 51.0 | 0.003081 |
| 98155 | 372.0 | 0.022472 |
| 98166 | 200.0 | 0.012082 |
| 98168 | 218.0 | 0.013169 |
| 98177 | 192.0 | 0.011598 |
| 98178 | 211.0 | 0.012746 |
| 98188 | 108.0 | 0.006524 |
| 98198 | 220.0 | 0.013290 |
| 98199 | 234.0 | 0.014136 |
| grade | porc | |
|---|---|---|
| grade | ||
| 1 | 1.0 | 0.000060 |
| 3 | 3.0 | 0.000181 |
| 4 | 23.0 | 0.001389 |
| 5 | 183.0 | 0.011055 |
| 6 | 1592.0 | 0.096170 |
| 7 | 7145.0 | 0.431618 |
| 8 | 4770.0 | 0.288148 |
| 9 | 1881.0 | 0.113628 |
| 10 | 698.0 | 0.042165 |
| 11 | 211.0 | 0.012746 |
| 12 | 40.0 | 0.002416 |
| 13 | 7.0 | 0.000423 |
| waterfront | porc | |
|---|---|---|
| waterfront | ||
| 0 | 16459.0 | 0.994261 |
| 1 | 95.0 | 0.005739 |
Estadistico Descriptico y Analisis
columnas_a_graficar = list(filter(lambda x: x!='price', clasificacion_columnas['numerica_continua']))
plots = [plot.scatter(du.data, x=columna, y='price') for columna in columnas_a_graficar]
plot.grid_subplot(*plots, cols = 2, titles=columnas_a_graficar).show()
clasificacion_columnas
{'categorica_ordinal': ['view', 'condition', 'zipcode', 'grade', 'waterfront'],
'fecha': ['date'],
'id': ['index'],
'numerica_continua': ['sqft_lot15',
'sqft_living15',
'sqft_living',
'sqft_lot',
'price'],
'numerica_discreta': ['bedrooms', 'antiguedad_venta', 'bathrooms', 'floors']}
columnas_a_graficar = clasificacion_columnas['categorica_ordinal'] + clasificacion_columnas['numerica_discreta']
nbins = [0 if len(du.data[x].unique()) < 20 else 20 for x in columnas_a_graficar]
plots = [
plot.box(du.data, x=column, y='price', nbins=nbin)
for column, nbin in zip(columnas_a_graficar, nbins)
]
plot.grid_subplot(*plots,
cols=2,
titles=columnas_a_graficar,
title='Box plot entre entradas categóricas y precio',
height=1500
).show()
De las variables categóricas, las que parecen tener un mayor impacto en el precio son grade, view y y waterfront. La variable condicion parece tener un aumento en el precio cuando es mayor a 3.
from pandas import DataFrame
corr_matrix: DataFrame = du.data.phik_matrix()
interval columns not set, guessing: ['zipcode', 'grade', 'view', 'bathrooms', 'bedrooms', 'sqft_living15', 'waterfront', 'floors', 'sqft_lot', 'price', 'condition', 'sqft_lot15', 'sqft_living', 'fue_renovada', 'antiguedad_venta']
corr_matrix['price'].sort_values(ascending=False)
price 1.000000 sqft_living 0.849294 grade 0.520938 sqft_living15 0.412334 bathrooms 0.397703 waterfront 0.335567 view 0.324725 floors 0.269777 sqft_lot15 0.200437 bedrooms 0.191491 sqft_lot 0.144752 zipcode 0.130665 fue_renovada 0.114474 antiguedad_venta 0.070706 condition 0.042359 Name: price, dtype: float64
Las variables relacionadas con el tamaño del apartamento y la calificación tienen mayor correlación con el precio del apartamento.
# Ordenando matriz de correlación con respecto a precio
corr_matrix = corr_matrix.sort_values(by='price', ascending=False)
corr_matrix = corr_matrix.reindex(columns=corr_matrix.index)
sns.heatmap(corr_matrix, cmap='PuOr')
<AxesSubplot:>
columnas = ['sqft_living', 'sqft_above', 'sqft_basement', 'antiguedad_venta', 'yr_built', 'yr_date', 'yr_renovated',
'fue_renovada', 'grade', 'sqft_living15', 'sqft_lot', 'tiene_sotano', 'condition', 'floors', 'bathrooms',
'view', 'bedrooms', 'waterfront', 'sqft_lot15', 'zipcode']
profiler2 = ProfileReport(du.data, explorative=True)
profiler_to_file(profiler2, '2_0_1 Perfilado de datos.html')
Ejecutando profiler
Summarize dataset: 0%| | 0/5 [00:00<?, ?it/s]
Generate report structure: 0%| | 0/1 [00:00<?, ?it/s]
Render HTML: 0%| | 0/1 [00:00<?, ?it/s]
Export report to file: 0%| | 0/1 [00:00<?, ?it/s]
True
calcular_descriptivas(du.data)
| zipcode | grade | view | bathrooms | bedrooms | sqft_living15 | waterfront | floors | sqft_lot | price | condition | sqft_lot15 | sqft_living | fue_renovada | antiguedad_venta | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 1.655400e+04 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 | 16554.000000 |
| mean | 98079.010269 | 7.583968 | 0.200254 | 1.713121 | 3.306572 | 1944.150840 | 0.005739 | 1.436873 | 9934.879667 | 5.106034e+05 | 3.408058 | 8996.247856 | 2016.595143 | 0.039205 | 43.645524 |
| std | 53.580311 | 1.103965 | 0.704138 | 0.674940 | 0.821211 | 649.237427 | 0.075539 | 0.552370 | 10957.363161 | 3.238058e+05 | 0.648368 | 7636.814694 | 848.705398 | 0.194088 | 29.345804 |
| min | 98001.000000 | 1.000000 | 0.000000 | 1.000000 | 1.000000 | 460.000000 | 0.000000 | 1.000000 | 520.000000 | 7.500000e+04 | 1.000000 | 659.000000 | 290.000000 | 0.000000 | -1.000000 |
| 25% | 98033.000000 | 7.000000 | 0.000000 | 1.000000 | 3.000000 | 1470.000000 | 0.000000 | 1.000000 | 5000.000000 | 3.160000e+05 | 3.000000 | 5011.250000 | 1400.000000 | 0.000000 | 18.000000 |
| 50% | 98070.000000 | 7.000000 | 0.000000 | 2.000000 | 3.000000 | 1810.000000 | 0.000000 | 1.000000 | 7480.000000 | 4.400000e+05 | 3.000000 | 7500.000000 | 1880.000000 | 0.000000 | 40.000000 |
| 75% | 98118.000000 | 8.000000 | 0.000000 | 2.000000 | 4.000000 | 2300.000000 | 0.000000 | 2.000000 | 10140.000000 | 6.200000e+05 | 4.000000 | 9750.000000 | 2478.750000 | 0.000000 | 63.000000 |
| max | 98199.000000 | 13.000000 | 4.000000 | 4.000000 | 5.000000 | 5790.000000 | 1.000000 | 3.000000 | 137214.000000 | 7.700000e+06 | 5.000000 | 57140.000000 | 12050.000000 | 1.000000 | 115.000000 |
| rango | 198.000000 | 12.000000 | 4.000000 | 3.000000 | 4.000000 | 5330.000000 | 1.000000 | 2.000000 | 136694.000000 | 7.625000e+06 | 4.000000 | 56481.000000 | 11760.000000 | 1.000000 | 116.000000 |
| IQR | 85.000000 | 1.000000 | 0.000000 | 1.000000 | 1.000000 | 830.000000 | 0.000000 | 1.000000 | 5140.000000 | 3.040000e+05 | 1.000000 | 4738.750000 | 1078.750000 | 0.000000 | 45.000000 |
| coef de var | 0.000546 | 0.145566 | 3.516232 | 0.393982 | 0.248357 | 0.333944 | 13.162944 | 0.384425 | 1.102919 | 6.341630e-01 | 0.190246 | 0.848889 | 0.420861 | 4.950597 | 0.672367 |
| skewness | 0.372547 | 0.716523 | 3.707596 | 0.644071 | 0.045938 | 1.059620 | 13.087759 | 0.776642 | 4.698177 | 4.658301e+00 | 1.038248 | 3.168057 | 1.323768 | 4.748876 | 0.448007 |
| kurtosis | -0.884930 | 1.371933 | 13.379400 | 0.237942 | -0.103048 | 1.455089 | 169.309900 | -0.465117 | 31.240935 | 5.059510e+01 | 0.540616 | 11.847458 | 4.451355 | 20.554309 | -0.669613 |
profiler3 = ProfileReport(du.data, explorative=True)
profiler_to_file(profiler3, '2_0_2 Transformacion_final.html')
profiler3
Ejecutando profiler
Summarize dataset: 0%| | 0/5 [00:00<?, ?it/s]
Generate report structure: 0%| | 0/1 [00:00<?, ?it/s]
Render HTML: 0%| | 0/1 [00:00<?, ?it/s]
Export report to file: 0%| | 0/1 [00:00<?, ?it/s]
du.data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 16554 entries, 19857 to 237 Data columns (total 15 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 zipcode 16554 non-null int32 1 grade 16554 non-null int32 2 view 16554 non-null int32 3 bathrooms 16554 non-null float64 4 bedrooms 16554 non-null float64 5 sqft_living15 16554 non-null float64 6 waterfront 16554 non-null int32 7 floors 16554 non-null float64 8 sqft_lot 16554 non-null float64 9 price 16554 non-null float64 10 condition 16554 non-null int32 11 sqft_lot15 16554 non-null float64 12 sqft_living 16554 non-null float64 13 fue_renovada 16554 non-null int32 14 antiguedad_venta 16554 non-null float64 dtypes: float64(9), int32(6) memory usage: 1.6 MB
variables_entrada = ['zipcode', 'grade', 'view', 'bathrooms', 'bedrooms', 'sqft_living15', 'waterfront', 'floors', 'sqft_lot', 'condition', 'sqft_lot15', 'sqft_living', 'fue_renovada', 'antiguedad_venta']
variable_salida = 'price'
du.data = du.data[variables_entrada + [variable_salida]]
y_name = 'price'
x_names = [columna for columna in du.data.columns if not columna == 'price']
Se hace seleccion de los mejores modelos usando el Training Set y k-fold Cross Validation
alpha = 1
l1_ratio=0.5
normalize=False
max_iter=100000
warm_start=True
modelos_a_probar = {
'Linear_Regression': {'modelo': make_pipeline(StandardScaler(), LinearRegression())},
'Linear_Regression degree 2': {'modelo': make_pipeline(
PolynomialFeatures(degree=2),
LinearRegression()
)},
'Linear_Regression degree 3': {'modelo': make_pipeline(
PolynomialFeatures(degree=3),
LinearRegression()
)},
'Linear_Regression degree 2 with normalization': {'modelo': make_pipeline(
StandardScaler(),
PolynomialFeatures(degree=2),
LinearRegression()
)},
'Lasso': {'modelo': Lasso()},
'Ridge': {'modelo': make_pipeline(
StandardScaler(),
Ridge()
)},
'Ridge degree 2': {'modelo': make_pipeline(
StandardScaler(),
PolynomialFeatures(degree=2),
Ridge()
)},
'ElasticNet': {'modelo': make_pipeline(
StandardScaler(),
ElasticNet(alpha=alpha, l1_ratio=l1_ratio, max_iter=max_iter, warm_start=warm_start)
)},
'SGD': {'modelo': make_pipeline(StandardScaler(), SGDRegressor())},
'SVR': {'modelo': make_pipeline(StandardScaler(), SVR())}
}
for nombre_modelo, dic_modelo in tqdm(modelos_a_probar.items(), desc='Realizando cross validation...'):
inicial = datetime.now()
modelo = dic_modelo['modelo']
dic_modelo['scores'] = cross_val_score(modelo, du.data[x_names], du.data[y_name], cv=5, scoring='r2')
tiempo_entrenamiento = (datetime.now() - inicial).total_seconds()
dic_modelo['tiempo_entrenamiento'] = tiempo_entrenamiento
dic_modelo['media'] = np.mean(dic_modelo['scores'])
dic_modelo['std'] = np.std(dic_modelo['scores'])
Realizando cross validation...: 100%|██████████████████████████████████████████████████████████████████████████████| 10/10 [01:25<00:00, 8.59s/it]
tabla_comparativa = pd.DataFrame(modelos_a_probar).transpose()
print('Comparativa R2')
tabla_comparativa.drop(columns=['scores','modelo']).sort_values(by='media', ascending=False)
Comparativa R2
| tiempo_entrenamiento | media | std | |
|---|---|---|---|
| Linear_Regression degree 2 with normalization | 0.971336 | 0.721899 | 0.017641 |
| Ridge degree 2 | 0.27502 | 0.721759 | 0.017615 |
| Linear_Regression degree 2 | 1.081678 | 0.721753 | 0.017614 |
| Ridge | 0.080008 | 0.621266 | 0.015699 |
| Linear_Regression | 0.105011 | 0.621266 | 0.015701 |
| Lasso | 0.720382 | 0.621265 | 0.015702 |
| SGD | 0.25253 | 0.618786 | 0.017617 |
| ElasticNet | 0.300569 | 0.575868 | 0.01368 |
| Linear_Regression degree 3 | 6.551879 | 0.240802 | 0.311652 |
| SVR | 75.562808 | -0.045957 | 0.006827 |
Se escogerá el Ridge degree 2 pues aunque el regresor lineal de grado 2 con normalización obtuvo una media ligeramente mayor, el modelo Ridge de grado 2 lo superó bastante en tiempo de entrenamiento. Como segundo modelo para la optimización de hiper parámetros se utilizará Linear_Regression degree 2 with normalization
mejor_modelo1 = modelos_a_probar['Ridge degree 2']['modelo']
mejor_modelo2 = modelos_a_probar['Linear_Regression degree 2 with normalization']['modelo']
mejor_modelo1.fit(du.data[x_names], du.data[y_name])
Pipeline(steps=[('standardscaler', StandardScaler()),
('polynomialfeatures', PolynomialFeatures()),
('ridge', Ridge())])
y_predict = mejor_modelo1.predict(du.data[x_names])
y_real = du.data['price']
vector = np.linspace(y_real.min()*0.8, y_real.max()*1.2)
fig1 = plot.scatter(pd.DataFrame({'y_real': y_real, 'y_pred': y_predict}), x='y_real', y='y_pred')
fig2 = px.line(pd.DataFrame({'y_real': vector, 'y_pred': vector}), x='y_real', y='y_pred')
plot.combine_plots(fig2, fig1).show()
Se seleccionan solo los mejores modelos para realizar el ajuste de hiperparametros, ya que tiene una carga computacional alta.
Al final se obtienen los parametros del mejor modelo
# Obteniendo el nombre de los parámetros del primer modelo
mejor_modelo1.get_params()
{'memory': None,
'steps': [('standardscaler', StandardScaler()),
('polynomialfeatures', PolynomialFeatures()),
('ridge', Ridge())],
'verbose': False,
'standardscaler': StandardScaler(),
'polynomialfeatures': PolynomialFeatures(),
'ridge': Ridge(),
'standardscaler__copy': True,
'standardscaler__with_mean': True,
'standardscaler__with_std': True,
'polynomialfeatures__degree': 2,
'polynomialfeatures__include_bias': True,
'polynomialfeatures__interaction_only': False,
'polynomialfeatures__order': 'C',
'ridge__alpha': 1.0,
'ridge__copy_X': True,
'ridge__fit_intercept': True,
'ridge__max_iter': None,
'ridge__normalize': 'deprecated',
'ridge__positive': False,
'ridge__random_state': None,
'ridge__solver': 'auto',
'ridge__tol': 0.001}
# Obteniendo el nombre de los parámetros del segundo modelo
mejor_modelo2.get_params()
{'memory': None,
'steps': [('standardscaler', StandardScaler()),
('polynomialfeatures', PolynomialFeatures()),
('linearregression', LinearRegression())],
'verbose': False,
'standardscaler': StandardScaler(),
'polynomialfeatures': PolynomialFeatures(),
'linearregression': LinearRegression(),
'standardscaler__copy': True,
'standardscaler__with_mean': True,
'standardscaler__with_std': True,
'polynomialfeatures__degree': 2,
'polynomialfeatures__include_bias': True,
'polynomialfeatures__interaction_only': False,
'polynomialfeatures__order': 'C',
'linearregression__copy_X': True,
'linearregression__fit_intercept': True,
'linearregression__n_jobs': None,
'linearregression__normalize': 'deprecated',
'linearregression__positive': False}
model1_param_grid = [
{
'polynomialfeatures__interaction_only': [True, False],
'ridge__alpha': np.linspace(0.01, 6, 60)
}
]
model2_param_grid = [
{
'polynomialfeatures__interaction_only': [True, False],
'polynomialfeatures__include_bias': [True, False],
'linearregression__positive': [True, False]
}
]
from sklearn.model_selection import GridSearchCV
gs1 = GridSearchCV(mejor_modelo1, model1_param_grid, scoring='r2')
gs2 = GridSearchCV(mejor_modelo2, model2_param_grid, scoring='r2')
# Realizando un grid search para el primer pipeline
print('Grid search primer modelo')
gs1.fit(du.data[x_names], du.data[y_name])
print('Grid search segundo modelo')
# Realizando un grid search para el segundo pipeline
gs2.fit(du.data[x_names], du.data[y_name])
print('Listo')
Grid search primer modelo Grid search segundo modelo Listo
print('Los mejores parámetros para el primer pipeline son:')
print(gs1.best_params_)
print()
print('Los mejores parámetros para el segundo pipeline son:')
print(gs2.best_params_)
Los mejores parámetros para el primer pipeline son:
{'polynomialfeatures__interaction_only': False, 'ridge__alpha': 6.0}
Los mejores parámetros para el segundo pipeline son:
{'linearregression__positive': False, 'polynomialfeatures__include_bias': True, 'polynomialfeatures__interaction_only': False}
from sklearn.compose import make_column_transformer
modelo_optimizado = make_pipeline(
make_column_transformer(
('passthrough', [
'zipcode', 'grade', 'view', 'bathrooms', 'bedrooms', 'sqft_living15', 'waterfront', 'floors',
'sqft_lot', 'condition', 'sqft_lot15', 'sqft_living', 'fue_renovada', 'antiguedad_venta'
])
),
StandardScaler(),
PolynomialFeatures(degree=2, interaction_only=False),
Ridge(alpha=6.0)
)
Después de haber obtenido el flujo para la transformación de los datos, se empaquetó en un conjunto de pipelines de transformación.
set_validacion = du.load_data(du.raw_validation_path)
set_entrenamiento = du.load_data(du.raw_train_test_path)
from src.data.procesamiento_datos import Preprocesamiento
pval = Preprocesamiento(['price'], ['price'])
set_validacion_transformado = set_validacion.pipe(preprocessing).pipe(pval.transform).pipe(build_features)[[variable_salida] + variables_entrada]
set_entrenamiento_transformado = set_entrenamiento.pipe(preprocessing).pipe(pval.transform).pipe(build_features)[[variable_salida] + variables_entrada]
modelo_optimizado.fit(set_entrenamiento_transformado[variables_entrada], set_entrenamiento_transformado[variable_salida])
y_real_train, y_predict_train = set_entrenamiento_transformado[variable_salida], modelo_optimizado.predict(set_entrenamiento_transformado[variables_entrada])
y_real_validation, y_predict_validation = set_validacion_transformado[variable_salida], modelo_optimizado.predict(set_validacion_transformado[variables_entrada])
r2_entrenamiento = r2_score(y_real_train, y_predict_train)
r2_validacion = r2_score(y_real_validation, y_predict_validation)
print(f'{r2_entrenamiento=:.2f}')
print(f'{r2_validacion=:.2f}')
r2_entrenamiento=0.73 r2_validacion=0.69
Se observa una disminución en el puntaje R2 de 4 puntos porcentuales, esto muestra que el modelo no tiene overfitting.
Con el análisis básico y el ajuste hecho, comienza el trabajo real (ingeniería).
El último paso para poner en produccion el modelo de prediccion sera:
El predictor final se puede serializar y grabar en el disco, de modo que la próxima vez que lo usemos, podemos omitir todo el entrenamiento y usar el modelo capacitado directamente:
#import pickle # Esta es una libreria de serializacion nativa de python, puede tener problemas de seguridad
df_completo = pd.read_csv(du.raw_path.joinpath('kc_house_dataDS.csv'), index_col=0, decimal='.')
# df_transformado = df_completo.pipe(li.fit_transform).pipe(pre_pro.fit_transform).pipe(pval.fit_transform).pipe(build_features)
# df_transformado = df_transformado[[variable_salida] + variables_entrada]
_columnas_numericas = [columna for columna in df_completo.columns if columna != 'date']
pre_pro = Preprocesamiento(['price', 'sqft_lot', 'sqft_lot15'], [])
li = LimpiezaCalidad(_columnas_numericas)
pda = ProcesamientoDatos()
pipeline_tranformacion_prediccion = make_pipeline(
li, pre_pro, pda
)
pipeline_tranformacion_entrenamiento=make_pipeline(
li, pre_pro, pval, pda
)
df_transformado = pipeline_tranformacion_entrenamiento.fit_transform(df_completo)
pipeline_tranformacion_prediccion.fit(df_completo)
modelo_optimizado.fit(df_transformado[variables_entrada], df_transformado[variable_salida])
# Guardando
# garbar el modelo en un archivo
joblib.dump(pda, du.model_path.with_stem('pda'))
du.model = modelo_optimizado
Realizando la validación del modelo con los datos completos.
from sklearn.pipeline import Pipeline
df = pd.read_csv(du.raw_path.joinpath('kc_house_dataDS.csv'), index_col=0, decimal='.')
li = LimpiezaCalidad(_columnas_numericas)
pre_pro = Preprocesamiento(['price', 'sqft_lot', 'sqft_lot15'], [])
pda: ProcesamientoDatos = joblib.load(du.model_path.with_stem('pda'))
pipeline_tranformacion_prediccion = make_pipeline(
li, pre_pro, pda
)
pipeline_tranformacion_validacion = make_pipeline(
li, pre_pro, pval, pda
)
model: Pipeline = joblib.load(du.model_path)
df_transformado = pipeline_tranformacion_validacion.transform(df)
y_predict = model.predict(df_transformado)
y_real = df_transformado['price']
is_outlier = y_real.isna()
print(f'{r2_score(y_real[~is_outlier], y_predict[~is_outlier]):.2f}')
0.65
plots = [
plot.scatter(pd.DataFrame({'y_real_train': y_real_train, 'y_predict_train':y_predict_train}), x='y_real_train', y='y_predict_train'),
plot.scatter(pd.DataFrame({'y_real_train': y_real, 'y_predict_train':y_predict}), x='y_real_train', y='y_predict_train')
]
plots2 = [
plot.histogram(pd.DataFrame({'y_real_train': y_real_train, 'y_predict_train':y_predict_train}), x='y_real_train'),
plot.histogram(pd.DataFrame({'y_real': y_real, 'y_predict_train':y_predict}), x='y_real')
]
plot.grid_subplot(*plots,
cols=2,
titles=['División test-train', 'Dataset completo'],
title='Scater plot entre datos de entrenamiento y dataset completo',
height=500
).show()
plot.grid_subplot(*plots2,
cols=2,
titles=['División test-train', 'Dataset completo'],
title='Distribución entre datos de entrenamiento y dataset completo',
height=500
).show()
Se observa una disminución en el R2 al entrenar con los datos completos, al revisar la distribución de los datos, se observa que en la eliminación de datos con el z-score, quedaron por fuera precios mayores de 2 millones, esto causa una disminución en el cálculo del R2. Esto será solucionado en la implementación del modelo en código.
Los gráficos de dependencia parcial son una forma de ver el impacto que tiene una variable en la respuesta. Se realizará el gráfico para las variables más importantes identificadas anteriormente: sqft_living, grade, sqft_above, sqft_living, bathrooms, view.
import matplotlib.pyplot as plt
from sklearn.inspection import partial_dependence
# from sklearn.inspection import plot_partial_dependence
from sklearn.inspection import PartialDependenceDisplay
from time import time
features = ['sqft_living', 'sqft_lot', 'sqft_lot15', 'grade', 'view', 'bathrooms', 'bedrooms', 'sqft_living15',
'waterfront', 'floors', 'condition', 'fue_renovada', 'antiguedad_venta']
x = PartialDependenceDisplay.from_estimator(model, df_transformado.drop(columns='price'),features= features)
print('Computing partial dependence plots...')
fig, ax = plt.subplots(figsize=(12, 15))
x.plot(ax=ax)
fig
Se puede observar que las siguientes variables no varían mucho el precio de venta de los hogares:
Las siguientes tienen un efecto moderado en la salida:
Las siguientes variables tienen un fuerte efecto en el precio
La siguiente variable tiene un leve impacto negativo en el precio
Recomendaciones Si actualmente se cuenta con una propiedad en Kansas y se tiene dinero disponible para invertir antes de realizar la venta, es recomendable antes de aumentar el tamaño del lote, invertir en la mejora del diseño de interior que ayude a mejorar los espacios y aumentar el área habitable pues se encontró que aunque la renovación aumenta el valor de las casas, si esta renovación no viene acompañada de un aumento en el valor estético de la misma no se logran obtener los mejores beneficios.
https://medium.com/@joserzapata/paso-a-paso-en-un-proyecto-machine-learning-bcdd0939d387
Proyecto de Principio a Final sobre readmision de pacientes con Diabetes
a-starter-pack-to-exploratory-data-analysis-with-python-pandas-seaborn-and-scikit-learn
a-data-science-for-good-machine-learning-project-walk-through-in-python-part-one
Docente: Jose R. Zapata